package service

import (
	"context"
	"fmt"
	"git.multiverse.io/eventkit/common/model/transaction"
	"git.multiverse.io/eventkit/common/util"
	"git.multiverse.io/eventkit/dxc-server/client"
	"git.multiverse.io/eventkit/dxc-server/config"
	dxcConstant "git.multiverse.io/eventkit/dxc-server/constant"
	"git.multiverse.io/eventkit/dxc-server/models"
	"git.multiverse.io/eventkit/dxc-server/repo"
	"git.multiverse.io/eventkit/kit/constant"
	"git.multiverse.io/framework/log"
	"github.com/go-errors/errors"
	uuid "github.com/satori/go.uuid"
	"strings"
	"time"
)

type Service interface {
	Begin(request transaction.RootTxnBeginRequest, paramData string, clientSDKVersion string) (string, error, int)
	Join(request transaction.BranchTxnJoinRequest, paramData string, clientSDKVersion string) (string, error, int)
	End(request transaction.TxnEndRequest) (error, int, int)
}

type DefaultService struct {
	Repository      repo.Repository
	CallbackService CallbackService
	TxnMode         dxcConstant.TxnMode
	Ctx             context.Context
}

func NewDefaultService(txnMode dxcConstant.TxnMode, repository repo.Repository, ctx context.Context) Service {
	return &DefaultService{
		Repository:      repository,
		CallbackService: NewCallbackService(repository, client.DefaultClient, ctx),
		TxnMode:         txnMode,
		Ctx:             ctx,
	}
}

// @Desc Begin root transaction
// @Param request RootTxnRegisterRequest
// @Return rootXid transaction's rootXid
// @Return err error
func (d *DefaultService) Begin(request transaction.RootTxnBeginRequest, paramData string, clientSDKVersion string) (string, error, int) {
	rootXid := request.Request.RootXid
	parentXid := request.Request.ParentXid
	if rootXid == "" {
		rootXid = uuid.NewV4().String()
		if parentXid == "" {
			parentXid = rootXid
		}
	}
	var txn models.Transaction
	participantAddressArray := strings.Split(request.Request.ParticipantAddress, constant.ParticipantAddressSplitChar)
	if strings.EqualFold(constant.CommDirect, participantAddressArray[0]) {
		txn = models.Transaction{
			RootXid:   rootXid,
			ParentXid: parentXid,
			BranchXid: rootXid,
			/* comm type + "|" + Callback root url + "|" + confirm path + "|" + cancel path */
			ParticipantAddress: request.Request.ParticipantAddress,
			BranchTxnStat:      dxcConstant.BRANCH_TXN_STATUS_TRYING,
			BranchTxnStartTime: util.ToTime(request.Request.RequestTime),
			ServiceName:        request.Request.ServiceName,
			ParamData:          paramData,
			ClientSDKVersion:   clientSDKVersion,
			Headers:            request.Request.Headers,
		}
	} else {
		dxcContext := models.DxcContextFromCtx(d.Ctx)
		/* comm type + "|" + Org + "/" + "WKS" + "/" + "ENV" + "/" + "SU" + "/" + "NodeId" + "/" + "InstanceId" + "|" + confirm topicId + "|" + cancel topicId*/
		requestParticipantAddress := participantAddressArray[0] + "|" +
			dxcContext.SrcOrg + "/" +
			dxcContext.SrcWorkspace + "/" +
			dxcContext.SrcEnvironment + "/" +
			dxcContext.SrcSu + "/" +
			config.Cfg.Service.CallbackNodeId + "/" +
			dxcContext.SrcInstanceId + "|" +
			participantAddressArray[1] + "|" +
			participantAddressArray[2]

		txn = models.Transaction{
			RootXid:            rootXid,
			ParentXid:          parentXid,
			BranchXid:          rootXid,
			ParticipantAddress: requestParticipantAddress,
			BranchTxnStat:      dxcConstant.BRANCH_TXN_STATUS_TRYING,
			BranchTxnStartTime: util.ToTime(request.Request.RequestTime),
			ServiceName:        request.Request.ServiceName,
			ParamData:          paramData,
			ClientSDKVersion:   clientSDKVersion,
			Environment:        dxcContext.SrcEnvironment,
			Workspace:          dxcContext.SrcWorkspace,
			Headers:            request.Request.Headers,
		}
	}
	// check exist
	existed, err := d.Repository.IsExistsRootTransaction(rootXid)
	if err != nil {
		return rootXid, err, -1
	}
	if existed {
		err = errors.Errorf("Begin txn{RootXid:[%s]} this root txn has exist", rootXid)
		return rootXid, err, constant.TxnBeginRootXidAlreadyExists
	}
	if _, err := d.Repository.AddRoot(&txn); err != nil {
		return rootXid, err, -1
	}
	return rootXid, nil, 0
}

// @Desc Enlist branch transaction
// @Param request BranchTxnEnlistRequest
// @Return branchXid transaction's branchXid
// @Return err error
func (d *DefaultService) Join(request transaction.BranchTxnJoinRequest, paramData string, clientSDKVersion string) (string, error, int) {
	rootXid := request.Request.RootXid
	parentXid := request.Request.ParentXid
	branchXid := request.Request.BranchXid
	if branchXid == "" {
		branchXid = uuid.NewV4().String()
	}

	var txn models.Transaction
	participantAddressArray := strings.Split(request.Request.ParticipantAddress, "|")
	if strings.EqualFold(constant.CommDirect, participantAddressArray[0]) {
		txn = models.Transaction{
			RootXid:   rootXid,
			ParentXid: parentXid,
			BranchXid: branchXid,
			/* comm type + "|" + Callback root url + "|" + confirm path + "|" + cancel path */
			ParticipantAddress: request.Request.ParticipantAddress,
			BranchTxnStat:      dxcConstant.BRANCH_TXN_STATUS_TRYING,
			BranchTxnStartTime: util.ToTime(request.Request.RequestTime),
			ServiceName:        request.Request.ServiceName,
			ParamData:          paramData,
			ClientSDKVersion:   clientSDKVersion,
			Headers:            request.Request.Headers,
		}
	} else {
		dxcContext := models.DxcContextFromCtx(d.Ctx)
		/* comm type + "|" + Org + "/" + "WKS" + "/" + "ENV" + "/" + "SU" + "/" + "NodeId" + "/" + "InstanceId" + "|" + confirm topicId + "|" + cancel topicId*/
		requestParticipantAddress := participantAddressArray[0] + "|" +
			dxcContext.SrcOrg + "/" +
			dxcContext.SrcWorkspace + "/" +
			dxcContext.SrcEnvironment + "/" +
			dxcContext.SrcSu + "/" +
			config.Cfg.Service.CallbackNodeId + "/" +
			dxcContext.SrcInstanceId + "|" +
			participantAddressArray[1] + "|" +
			participantAddressArray[2]

		txn = models.Transaction{
			RootXid:            rootXid,
			ParentXid:          parentXid,
			BranchXid:          branchXid,
			ParticipantAddress: requestParticipantAddress,
			BranchTxnStat:      dxcConstant.BRANCH_TXN_STATUS_TRYING,
			BranchTxnStartTime: util.ToTime(request.Request.RequestTime),
			ServiceName:        request.Request.ServiceName,
			ParamData:          paramData,
			ClientSDKVersion:   clientSDKVersion,
			Environment:        dxcContext.SrcEnvironment,
			Workspace:          dxcContext.SrcWorkspace,
			Headers:            request.Request.Headers,
		}
	}

	if err := d.Repository.TryEnList(rootXid); err != nil {
		return branchXid, err, -1
	}

	// check exist
	existed, err := d.Repository.IsExistsBranchTransaction(rootXid, branchXid)
	if err != nil {
		return branchXid, err, -1
	}
	if existed {
		err := fmt.Errorf("Join txn{RootXid:[%s], BranchXid:[%s]} this branch txn has exist", rootXid, branchXid)
		return branchXid, err, constant.TxnJoinFailedBranchXidAlreadyExists
	}
	if _, err := d.Repository.AddBranch(&txn); err != nil {
		return branchXid, err, -1
	}
	return branchXid, nil, 0
}

// @Desc transaction Report TryResult
// @Param request TxnTryResultReportRequest
// @Return error, errorCode, transaction branch totalCount
func (d *DefaultService) End(request transaction.TxnEndRequest) (error, int, int) {
	switch d.TxnMode {
	case dxcConstant.SOA:
		return d.doReportTryResultWithSoa(request)
	default:
		return errors.Errorf("only support SOA model"), -1, 0
	}
}

func (d *DefaultService) doReportTryResultWithSoa(request transaction.TxnEndRequest) (error, int, int) {
	rootXid := request.Request.RootXid
	branchXid := request.Request.BranchXid
	isOk := request.Request.Ok
	isRoot := rootXid == branchXid
	var err error

	// branch transaction report
	if !isRoot && isOk {
		return errors.Errorf("branch transaction try result is true, not need report try result"), -1, 0
	}

	if !isOk && request.Request.TryFailedIgnoreCallbackCancel {
		_, err = d.CallbackService.TryFailedIgnoreCallbackCancel(rootXid, branchXid)
		if err != nil {
			return errors.Errorf("branch transaction TryFailedIgnoreCallbackCancel report failed, err:%s", err), -1, 0
		}
	}

	if !isRoot {
		return nil, 0, 0
	}

	startTime := time.Now()

	var totalCount int
	var successCount int
	dxcContext := models.DxcContextFromCtx(d.Ctx)
	totalCount, successCount, err = d.CallbackService.GlobalTxnCallback(dxcContext.SpanCtx, rootXid, isOk)
	log.Debugsf("All branches callback, root Xid:[%s], total number of branches:[%d], total time cost:%v!", request.Request.RootXid, totalCount, time.Now().Sub(startTime))
	if err != nil {
		if SomeBranchCallbackFailed == err {
			return err, constant.TxnEndFailedBranchesNotAllCallbackSuccess, totalCount
		} else {
			return err, -1, totalCount
		}
	}

	if totalCount != successCount {
		return errors.Errorf("transactions not all callback success, total:[%d], success:[%d]", totalCount, successCount), constant.TxnEndFailedBranchesNotAllCallbackSuccess, totalCount
	}

	return nil, 0, totalCount
}
